#!/user/bin/env python
# coding=utf-8
"""
@project : face2face_train
@author  : huyi
@file   : app.py
@ide    : PyCharm
@time   : 2023-12-06 19:04:21
"""
# 导入必要的库
import os

# 设置工作目录
os.chdir('/code')
import time
import traceback
from enum import Enum
import requests  # 添加requests库用于发送webhook请求

# 导入自定义工具和第三方库
from y_utils.logger import logger
from flask import Flask, request
from y_utils.config import GlobalConfig
from service.trans_dh_service import TransDhTask, Status,a

import json
import threading
import gc
import cv2
import uuid as uuid_module  # 添加uuid库用于生成唯一文件名
import shutil

# 从环境变量获取token，默认为heygam778899
AUTH_TOKEN = os.environ.get('AUTH_TOKEN', 'heygam778899')
logger.info(f"服务启动，认证Token已设置")

def send_webhook_notifications(webhook_urls, data):
    """
    向webhook地址发送通知
    
    Args:
        webhook_urls: webhook地址列表
        data: 要发送的数据
    """
    headers = {'Content-Type': 'application/json'}
    
    # 使用异步方式发送webhook通知
    for webhook_url in webhook_urls:
        if webhook_url.find('https://qyapi.weixin.qq.com') != -1    :
            # 企业微信webhook
            json_data = {
                'msgtype': 'text',
                'text': {
                    'content': str(data)
                }
            }
        else:
            json_data = data
            
        def send_async_webhook(url, json_data):
            try:
                response = requests.post(url, json=json_data, headers=headers, timeout=5)
                if response.status_code != 200:
                    logger.error(f"Webhook通知发送失败 {url}: {response.status_code}\n{json_data}\n{response.text}")
                else:
                    logger.info(f"Webhook通知发送成功 {url}\n{json_data}\n{response.text}")
            except Exception as e:
                logger.error(f"Webhook通知发送异常 {url}: {str(e)}")
                logger.error(traceback.format_exc())
        
        # 创建并启动线程进行异步请求，设置为守护线程以便主程序退出时自动终止
        webhook_thread = threading.Thread(target=send_async_webhook, args=(webhook_url, data), daemon=True)
        webhook_thread.start()
        # 不保留线程引用，便于垃圾回收
        del webhook_thread

# 添加装饰器用于验证请求中的token
def require_auth(f):
    def decorated(*args, **kwargs):
        # 从请求头或查询参数中获取token
        token = request.headers.get('Authorization')
        if not token:
            token = request.args.get('token')
        
        # 对于POST请求，也可以从表单或JSON数据中获取
        if request.method == 'POST':
            if request.is_json and 'token' in request.json:
                token = request.json.get('token')
            elif request.form and 'token' in request.form:
                token = request.form.get('token')
        
        # 验证token
        if not token or token != AUTH_TOKEN:
            return json.dumps(
                {"code": 401, "success": False, "msg": "认证失败", "data": {}},
                ensure_ascii=False
            )
        
        return f(*args, **kwargs)
    
    # 保留原始函数名称
    decorated.__name__ = f.__name__
    return decorated

# 初始化Flask应用
app = Flask(__name__)

# 定义API文档路由
@app.route('/api', methods=['GET'])
def api_docs():
    """返回API接口文档页面"""
    api_list = [
        {
            "路径": "/easy/upload_audio",
            "方法": "POST",
            "描述": "上传音频文件",
            "参数": "file (表单文件), token (认证令牌)"   
        },
        {
            "路径": "/easy/list_audio",
            "方法": "GET",
            "描述": "查询上传音频列表",
            "参数": "keyword, page, pageSize, token (认证令牌)"
        },
        {
            "路径": "/easy/list_video",
            "方法": "GET",
            "描述": "查询视频列表",
            "参数": "keyword, page, pageSize, token (认证令牌)"
        },
        {
            "路径": "/easy/upload_video",
            "方法": "POST",
            "描述": "上传视频文件",
            "参数": "file (表单文件), token (认证令牌)"
        },
        {
            "路径": "/easy/submit",
            "方法": "POST",
            "描述": "提交任务",
            "参数": "audio_url, video_url, code, watermark_switch, digital_auth, chaofen, pn, token (认证令牌)"
        },
        {
            "路径": "/easy/query",
            "方法": "GET",
            "描述": "查询任务状态",
            "参数": "code, token (认证令牌)"
        },
        {
            "路径": "/easy/clear_gpu_memory",
            "方法": "POST",
            "描述": "释放GPU显存",
            "参数": "token (认证令牌)"
        },
        {
            "路径": "/easy/list_result",
            "方法": "GET",
            "描述": "查询结果文件列表",
            "参数": "keyword, page, pageSize, token (认证令牌)"
        },
        {
            "路径": "/easy/download_result/<filename>",
            "方法": "GET",
            "描述": "下载结果文件",
            "参数": "filename (路径参数), token (认证令牌)"
        },
        {
            "路径": "/easy/delete_result",
            "方法": "POST",
            "描述": "删除结果文件",
            "参数": "filename, token (认证令牌)"
        }
    ]
    
    html = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>API接口文档</title>
        <meta charset="utf-8">
        <style>
            body { font-family: Arial, sans-serif; margin: 20px; }
            h1 { color: #333; }
            table { border-collapse: collapse; width: 100%; }
            th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
            th { background-color: #f2f2f2; }
            tr:nth-child(even) { background-color: #f9f9f9; }
            .auth-info { background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px; border-left: 5px solid #4CAF50; }
        </style>
    </head>
    <body>
        <h1>API接口文档</h1>
        <div class="auth-info">
            <h3>认证说明</h3>
            <p>所有API接口都需要认证。认证方式有以下几种：</p>
            <ol>
                <li>在请求头中添加 <code>Authorization</code> 字段，值为认证token</li>
                <li>在URL查询参数中添加 <code>token</code> 字段，如：<code>?token=your_token</code></li>
                <li>在POST请求的表单或JSON数据中包含 <code>token</code> 字段</li>
            </ol>
            <p>默认token为：heygam778899（可通过环境变量 AUTH_TOKEN 修改）</p>
        </div>
        <table>
            <tr>
                <th>路径</th>
                <th>方法</th>
                <th>描述</th>
                <th>参数</th>
            </tr>
    """
    
    for api in api_list:
        html += f"""
            <tr>
                <td>{api['路径']}</td>
                <td>{api['方法']}</td>
                <td>{api['描述']}</td>
                <td>{api['参数']}</td>
            </tr>
        """
    
    html += """
        </table>
    </body>
    </html>
    """
    
    return html





# 定义响应类，用于统一返回格式
class EasyResponse:
    def __init__(
            self,
            code,
            success,
            msg, data: dict):
        self.code = code          # 响应码
        self.success = success    # 是否成功
        self.msg = msg            # 响应消息
        self.data = data          # 响应数据


# 定义响应码枚举类
class ResponseCode(Enum):
    system_error = [9999, '系统异常']
    success = [10000, '成功']
    busy = [10001, '忙碌中']
    error1 = [10002, '参数异常']
    error2 = [10003, '获取锁异常']
    error3 = [10004, '任务不存在']





# 查询上传音频接口列表
@app.route('/easy/list_audio', methods=['GET'])
@require_auth
def list_audio():
    try:
        # 获取请求参数
        keyword = request.args.get('keyword', '')
        page = int(request.args.get('page', 1))
        page_size = int(request.args.get('pageSize', 10))
        
        # 确保页码和每页数量为正数
        if page < 1:
            page = 1
        if page_size < 1:
            page_size = 10
        
        # 获取音频文件目录
        audio_dir = os.path.join('/code', 'data', 'voice', 'data')
        
        # 确保目录存在
        if not os.path.exists(audio_dir):
            return json.dumps({
                'code': ResponseCode.success.value[0],
                'message': '成功',
                'data': {
                    'rows': [],
                    'page': page,
                    'pageSize': page_size,
                    'totalPages': 0
                }
            }, ensure_ascii=False)
        
        # 获取所有音频文件
        all_files = []
        for file_name in os.listdir(audio_dir):
            file_path = os.path.join(audio_dir, file_name)
            if os.path.isfile(file_path):
                # 获取文件信息
                file_stats = os.stat(file_path)
                file_info = {
                    'fileName': file_name,
                    'filePath': f'data/voice/data/{file_name}',
                    'fileSize': file_stats.st_size,
                    'createTime': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(file_stats.st_ctime))
                }
                # 根据关键词过滤
                if keyword.lower() in file_name.lower():
                    all_files.append(file_info)
        
        # 计算总页数
        total_files = len(all_files)
        total_pages = (total_files + page_size - 1) // page_size
        
        # 对文件按创建时间排序（最新的在前）
        all_files.sort(key=lambda x: x['createTime'], reverse=True)
        
        # 分页
        start_idx = (page - 1) * page_size
        end_idx = min(start_idx + page_size, total_files)
        page_files = all_files[start_idx:end_idx]
        
        # 返回结果
        return json.dumps({
            'code': ResponseCode.success.value[0],
            'message': '成功',
            'data': {
                'rows': page_files,
                'page': page,
                'pageSize': page_size,
                'totalPages': total_pages
            }
        }, ensure_ascii=False)
    
    except Exception as e:
        # 处理异常
        logger.error(f"列出音频文件时出错: {str(e)}")
        traceback.print_exc()
        return json.dumps({
            'code': ResponseCode.system_error.value[0],
            'message': ResponseCode.system_error.value[1],
            'data': {
                'rows': [],
                'page': 1,
                'pageSize': 10,
                'totalPages': 0
            }
        }, ensure_ascii=False)


# 上传音频接口
@app.route('/easy/upload_audio', methods=['POST'])
@require_auth
def upload_audio():
    try:
        # 检查是否有文件上传
        if 'audio_file' not in request.files:
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '未找到音频文件', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        audio_file = request.files['audio_file']
        
        # 检查文件名是否为空
        if audio_file.filename == '':
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件名为空', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 获取是否覆盖同名文件的参数
        overwrite = request.form.get('overwrite', '0')
        overwrite = True if overwrite == '1' else False
        
        # 确保目标目录存在
        upload_dir = os.path.join('/code', 'data', 'voice', 'data')
        if not os.path.exists(upload_dir):
            os.makedirs(upload_dir)
        
        
        # 生成唯一文件名
        timestamp = time.strftime('%Y%m%d%H%M%S', time.localtime())
        unique_filename = f"{timestamp}_{uuid_module.uuid4().hex[:8]}_{audio_file.filename}"
        file_path = os.path.join(upload_dir, unique_filename)
        
        # 检查文件是否已存在且不覆盖
        if os.path.exists(file_path) and not overwrite:
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件已存在且未设置覆盖', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 保存文件
        audio_file.save(file_path)
        
        # 返回成功响应，确保返回路径以/code开头
        return_path = f'/code/data/voice/data/{unique_filename}'
        
        return json.dumps(
            EasyResponse(ResponseCode.success.value[0], True, '音频上传成功', 
                        {'file_path': return_path}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    
    except Exception as e:
        logger.error(f"上传音频文件时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)

# 查询合并完成视频接口列表
@app.route('/easy/list', methods=['GET'])
@require_auth
def easy_list():
    try:
        # 获取查询参数
        keyword = request.args.get('keyword', '')
        page = int(request.args.get('page', 1))
        page_size = int(request.args.get('pageSize', 10))
        
        # 确保页码和每页数量为正整数
        if page < 1:
            page = 1
        if page_size < 1:
            page_size = 10
        
        # 获取视频列表目录
        video_dir = os.path.join('/code', 'data', 'output')
        if not os.path.exists(video_dir):
            return json.dumps({
                'code': ResponseCode.success.value[0],
                'message': '查询成功',
                'data': {
                    'rows': [],
                    'page': page,
                    'pageSize': page_size,
                    'totalPages': 0
                }
            }, ensure_ascii=False, indent=4)
        
        # 获取所有视频文件
        video_files = []
        for root, dirs, files in os.walk(video_dir):
            for file in files:
                if file.endswith(('.mp4', '.avi', '.mov', '.mkv')):
                    # 如果有关键词，进行过滤
                    if keyword and keyword.lower() not in file.lower():
                        continue
                    
                    file_path = os.path.join(root, file)
                    rel_path = os.path.relpath(file_path, '/code')
                    
                    # 获取文件信息
                    stat_info = os.stat(file_path)
                    video_files.append({
                        'name': file,
                        'path': rel_path,
                        'size': stat_info.st_size,
                        'create_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat_info.st_ctime))
                    })
        
        # 按创建时间倒序排序
        video_files.sort(key=lambda x: x['create_time'], reverse=True)
        
        # 计算总页数
        total_count = len(video_files)
        total_pages = (total_count + page_size - 1) // page_size
        
        # 分页
        start_idx = (page - 1) * page_size
        end_idx = min(start_idx + page_size, total_count)
        paged_files = video_files[start_idx:end_idx]
        
        # 返回结果
        return json.dumps({
            'code': ResponseCode.success.value[0],
            'message': '查询成功',
            'data': {
                'rows': paged_files,
                'page': page,
                'pageSize': page_size,
                'totalPages': total_pages
            }
        }, ensure_ascii=False, indent=4)
    
    except Exception as e:
        logger.error(f"查询视频列表时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps({
            'code': ResponseCode.system_error.value[0],
            'message': f'系统异常: {str(e)}',
            'data': {
                'rows': [],
                'page': 1,
                'pageSize': 10,
                'totalPages': 0
            }
        }, ensure_ascii=False, indent=4)

# 上传视频接口
@app.route('/easy/upload_video', methods=['POST'])
@require_auth
def upload_video():
    try:
        import uuid as uuid_module  # 在函数内导入以确保可用性
        
        # 检查是否有文件上传
        if 'file' not in request.files:
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '没有上传文件', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        file = request.files['file']
        
        # 检查文件名是否为空
        if file.filename == '':
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件名为空', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 检查文件类型
        if not file.filename.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')):
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '不支持的视频格式', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 确保目标目录存在
        upload_dir = '/code/data/mp4'  # 修改为以/code开头
        if not os.path.exists(upload_dir):
            os.makedirs(upload_dir)
        
        # 生成唯一文件名
        timestamp = time.strftime('%Y%m%d%H%M%S', time.localtime())
        filename = f"{timestamp}_{uuid_module.uuid4().hex[:8]}_{file.filename}"
        file_path = os.path.join(upload_dir, filename)
        
        # 保存文件
        file.save(file_path)
        
        # 获取文件信息
        file_size = os.path.getsize(file_path)
        # 确保返回路径以/code开头
        rel_path = f'/code/data/mp4/{filename}'
        
        # 格式化文件大小
        size_str = f"{file_size} 字节"
        if file_size > 1024 * 1024 * 1024:
            size_str = f"{file_size / (1024 * 1024 * 1024):.2f} GB"
        elif file_size > 1024 * 1024:
            size_str = f"{file_size / (1024 * 1024):.2f} MB"
        elif file_size > 1024:
            size_str = f"{file_size / 1024:.2f} KB"
        
        return json.dumps(
            EasyResponse(ResponseCode.success.value[0], True, '视频上传成功', {
                'filename': filename,
                'path': rel_path,
                'size': size_str,
                'upload_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
            }),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    
    except Exception as e:
        logger.error(f"上传视频时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)

# 查询上传视频接口列表
@app.route('/easy/video_list', methods=['GET'])
@require_auth
def get_video_list():
    try:
        # 获取查询参数
        keyword = request.args.get('keyword', '')
        page = int(request.args.get('page', 1))
        page_size = int(request.args.get('pageSize', 10))
        
        # 确保页码和每页数量为正整数
        if page < 1:
            page = 1
        if page_size < 1:
            page_size = 10
        
        # 视频目录路径
        video_dir = '/data/mp4'
        if not os.path.exists(video_dir):
            return json.dumps(
                EasyResponse(ResponseCode.success.value[0], True, '视频目录不存在', {
                    'rows': [],
                    'page': page,
                    'pageSize': page_size,
                    'totalPages': 0,
                    'total': 0
                }),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 获取所有视频文件
        video_files = []
        for filename in os.listdir(video_dir):
            file_path = os.path.join(video_dir, filename)
            if os.path.isfile(file_path) and (filename.lower().endswith('.mp4') or 
                                             filename.lower().endswith('.avi') or 
                                             filename.lower().endswith('.mov') or
                                             filename.lower().endswith('.wmv')):
                # 如果有关键词，进行过滤
                if keyword and keyword.lower() not in filename.lower():
                    continue
                
                # 获取文件信息
                file_size = os.path.getsize(file_path)
                file_mtime = os.path.getmtime(file_path)
                
                # 格式化文件大小
                size_str = f"{file_size} 字节"
                if file_size > 1024 * 1024 * 1024:
                    size_str = f"{file_size / (1024 * 1024 * 1024):.2f} GB"
                elif file_size > 1024 * 1024:
                    size_str = f"{file_size / (1024 * 1024):.2f} MB"
                elif file_size > 1024:
                    size_str = f"{file_size / 1024:.2f} KB"
                
                # 添加到文件列表
                video_files.append({
                    'filename': filename,
                    'path': os.path.join('/data/mp4', filename),
                    'size': size_str,
                    'raw_size': file_size,
                    'upload_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(file_mtime))
                })
        
        # 按上传时间倒序排序
        video_files.sort(key=lambda x: x['upload_time'], reverse=True)
        
        # 计算总页数
        total = len(video_files)
        total_pages = (total + page_size - 1) // page_size
        
        # 分页处理
        start_idx = (page - 1) * page_size
        end_idx = min(start_idx + page_size, total)
        paginated_files = video_files[start_idx:end_idx]
        
        return json.dumps(
            EasyResponse(ResponseCode.success.value[0], True, '获取视频列表成功', {
                'rows': paginated_files,
                'page': page,
                'pageSize': page_size,
                'totalPages': total_pages,
                'total': total
            }),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    
    except Exception as e:
        logger.error(f"获取视频列表时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)



# 提交任务接口
@app.route('/easy/submit', methods=['POST'])
@require_auth
def easy_submit():
    # 解析请求数据
    request_data = json.loads(request.data)
    try:
        # 检查必要参数
        if 'audio_url' not in request_data or request_data['audio_url'] == '':
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, 'audio_url参数缺失', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        if 'video_url' not in request_data or request_data['video_url'] == '':
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, 'video_url参数缺失', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        if 'code' not in request_data or request_data['code'] == '':
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, 'code参数缺失', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        # 获取必要参数
        _audio_url = request_data['audio_url']
        _video_url = request_data['video_url']
        _code = request_data['code']
        
        # 获取webhook_urls参数，默认为空列表
        _webhook_urls = request_data.get('webhook_urls', [])
        if isinstance(_webhook_urls, str):
            try:
                _webhook_urls = json.loads(_webhook_urls)
            except:
                _webhook_urls = [_webhook_urls] if _webhook_urls else []
        elif not isinstance(_webhook_urls, list):
            _webhook_urls = []

        # 处理可选参数：水印开关
        if 'watermark_switch' not in request_data or request_data['watermark_switch'] == '':
            _watermark_switch = 0
        else:
            if str(request_data['watermark_switch']) == '1':
                _watermark_switch = 1
            else:
                _watermark_switch = 0
        # 处理可选参数：数字认证
        if 'digital_auth' not in request_data or request_data['digital_auth'] == '':
            _digital_auth = 0
        else:
            if str(request_data['digital_auth']) == '1':
                _digital_auth = 1
            else:
                _digital_auth = 0
        # 处理可选参数：超分
        if 'chaofen' not in request_data or request_data['chaofen'] == '':
            _chaofen = 0
        else:
            if str(request_data['chaofen']) == '1':
                _chaofen = 1
            else:
                _chaofen = 0
        # 检查系统是否忙碌
        if TransDhTask.instance().run_flag:
            return json.dumps(
                EasyResponse(ResponseCode.busy.value[0], True, ResponseCode.busy.value[1], {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        # 处理可选参数：pn
        if 'pn' not in request_data or request_data['pn'] == '':
            _pn = 1
        else:
            if str(request_data['pn']) == '1':
                _pn = 1
            else:
                _pn = 0
        # 再次检查系统是否忙碌（防止并发请求）
        if TransDhTask.instance().run_flag:
            return json.dumps(
                EasyResponse(ResponseCode.busy.value[0], True, ResponseCode.busy.value[1], {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        else:
            # 尝试获取锁并设置任务状态
            try:
                TransDhTask.instance().run_lock.acquire()
                TransDhTask.instance().run_flag = True
                TransDhTask.instance().task_dic[_code] = (Status.run, 0, '', '')

                # 存储webhook_urls到任务字典中
                # 初始化webhook_urls字典，如果不存在
                if not hasattr(TransDhTask.instance(), 'webhook_urls_dic'):
                    TransDhTask.instance().webhook_urls_dic = {}
                
                # 存储webhook_urls到任务字典中
                _webhook_urls = request_data.get('webhook_urls', [])
                if isinstance(_webhook_urls, str):
                    try:
                        _webhook_urls = json.loads(_webhook_urls)
                    except:
                        _webhook_urls = [_webhook_urls] if _webhook_urls else []
                elif not isinstance(_webhook_urls, list):
                    _webhook_urls = []
                
                logger.info(f"任务 {_code} 设置了 {len(_webhook_urls)} 个webhook通知地址")
                TransDhTask.instance().webhook_urls_dic[_code] = _webhook_urls
                
            except Exception as e:
                traceback.print_exc()
                # 发送错误通知到webhook
                if _webhook_urls:
                    error_msg = f"获取任务锁失败: {str(e)}"
                    send_webhook_notifications(_webhook_urls, {
                        'code': _code,
                        'status': 'error',
                        'message': error_msg
                    })
                return json.dumps(
                    EasyResponse(ResponseCode.error2.value[0], False, ResponseCode.error2.value[1], {}),
                    default=lambda obj: obj.__dict__,
                    sort_keys=True, ensure_ascii=False,
                    indent=4)
            finally:
                # 释放锁
                TransDhTask.instance().run_lock.release()

            # 启动工作线程处理任务
            threading.Thread(
                target=TransDhTask.instance().work,
                args=(_audio_url, _video_url, _code, _watermark_switch, _digital_auth, _chaofen, _pn)).start()
            
            filename = _code + '-r.mp4'
            file_path = os.path.join('/code/data/result', filename)

            # 返回成功响应
            return json.dumps(
                EasyResponse(ResponseCode.success.value[0], True, ResponseCode.success.value[0], {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
    except Exception as e:
        # 处理异常
        traceback.print_exc()
        # 发送错误通知到webhook
        if _webhook_urls:
            send_webhook_notifications(_webhook_urls, {
                'code': _code,
                'status': 'error',
                'message': str(e)
            })
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, ResponseCode.system_error.value[1], {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    finally:
        # 垃圾回收
        gc.collect()


# 查询任务状态接口
@app.route('/easy/query', methods=['GET'])
@require_auth
def easy_query():
    del_flag = False  # 是否需要删除任务标志
    # 获取请求参数
    get_data = request.args.to_dict()
    try:
        # 检查必要参数
        if get_data.get('code') is None and get_data.get('code') == '':
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, 'code参数缺失', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        _code = get_data.get('code')
        # 检查任务是否存在
        if _code in TransDhTask.instance().task_dic:
            # 获取任务状态
            _status, _progress, _result, _msg = TransDhTask.instance().task_dic[_code]

            # 根据任务状态返回不同响应
            if _status == Status.run:
                # 任务运行中
                return json.dumps(
                    EasyResponse(ResponseCode.success.value[0], True, '', {
                        'code': _code,
                        'status': _status.value,
                        'progress': _progress,
                        'result': _result,
                        'msg': _msg
                    }),
                    default=lambda obj: obj.__dict__,
                    sort_keys=True, ensure_ascii=False,
                    indent=4)
            elif _status == Status.success:
                # 任务成功完成，标记需要删除
                del_flag = True
                _result = os.path.join('/code/data/result', _code + '-r.mp4')
                _temp = os.path.join('/code/data/temp', _code + '-r.mp4')
                if not os.path.exists(_result) and  os.path.exists(_temp):
                    #_temp 存在，_result 不存在，则将 _temp 迁移到 _result
                    shutil.move(_temp, _result)
                    
                return json.dumps(
                    EasyResponse(ResponseCode.success.value[0], True, '', {
                        'code': _code,
                        'status': _status.value,
                        'progress': _progress,
                        'result': _result,
                        'msg': _msg
                    }),
                    default=lambda obj: obj.__dict__,
                    sort_keys=True, ensure_ascii=False,
                    indent=4)
            elif _status == Status.error:
                # 任务出错，标记需要删除
                del_flag = True
                return json.dumps(
                    EasyResponse(ResponseCode.success.value[0], True, '', {
                        'code': _code,
                        'status': _status.value,
                        'progress': _progress,
                        'result': _result,
                        'msg': _msg
                    }),
                    default=lambda obj: obj.__dict__,
                    sort_keys=True, ensure_ascii=False, indent=4)
        else:
            # 任务不存在
            return json.dumps(
                EasyResponse(ResponseCode.error3.value[0], True, ResponseCode.error3.value[1], {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
    except Exception as e:
        # 处理异常
        traceback.print_exc()
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, ResponseCode.system_error.value[1], {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    finally:
        # 如果需要删除任务，则删除
        if del_flag:
            try:
                TransDhTask.instance().run_lock.acquire()
                del TransDhTask.instance().task_dic[_code]
                TransDhTask.instance().run_lock.release()
            except Exception as e:
                traceback.print_exc()
                return json.dumps(
                    EasyResponse(ResponseCode.error3.value[0], True, ResponseCode.error3.value[1], {}),
                    default=lambda obj: obj.__dict__,
                    sort_keys=True, ensure_ascii=False,
                    indent=4)


# 清理临时文件接口
@app.route('/easy/clean_temp', methods=['POST'])
@require_auth
def clean_temp():
    try:
        # 获取临时目录路径
        temp_dir = os.path.join('/code', 'data', 'temp')
        
        # 检查目录是否存在
        if not os.path.exists(temp_dir):
            return json.dumps(
                EasyResponse(ResponseCode.success.value[0], True, '临时目录不存在，无需清理', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 记录清理前的文件数量和总大小
        files_count = 0
        total_size = 0
        
        # 遍历临时目录中的所有文件
        for root, dirs, files in os.walk(temp_dir, topdown=False):
            for file in files:
                file_path = os.path.join(root, file)
                try:
                    # 获取文件大小
                    file_size = os.path.getsize(file_path)
                    total_size += file_size
                    
                    # 删除文件
                    os.remove(file_path)
                    files_count += 1
                except Exception as e:
                    logger.error(f"删除文件 {file_path} 时出错: {str(e)}")
            
            # 删除空目录（从下往上删除）
            for dir in dirs:
                dir_path = os.path.join(root, dir)
                try:
                    os.rmdir(dir_path)
                except Exception as e:
                    logger.error(f"删除目录 {dir_path} 时出错: {str(e)}")
        
        # 尝试删除根临时目录下的文件
        for file in os.listdir(temp_dir):
            file_path = os.path.join(temp_dir, file)
            if os.path.isfile(file_path):
                try:
                    file_size = os.path.getsize(file_path)
                    total_size += file_size
                    os.remove(file_path)
                    files_count += 1
                except Exception as e:
                    logger.error(f"删除文件 {file_path} 时出错: {str(e)}")
        
        # 转换总大小为可读格式
        size_str = f"{total_size} 字节"
        if total_size > 1024 * 1024 * 1024:
            size_str = f"{total_size / (1024 * 1024 * 1024):.2f} GB"
        elif total_size > 1024 * 1024:
            size_str = f"{total_size / (1024 * 1024):.2f} MB"
        elif total_size > 1024:
            size_str = f"{total_size / 1024:.2f} KB"
        
        # 返回清理结果
        return json.dumps(
            EasyResponse(ResponseCode.success.value[0], True, '临时目录清理成功', {
                'files_removed': files_count,
                'total_size_cleaned': size_str,
                'clean_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
            }),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    
    except Exception as e:
        logger.error(f"清理临时目录时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)

@app.route('/easy/test_auth', methods=['GET'])
@require_auth
def test_auth():
    """测试认证接口"""
    return json.dumps(
        EasyResponse(ResponseCode.success.value[0], True, '认证成功', {
            'timestamp': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()),
            'message': '您的认证令牌有效'
        }),
        default=lambda obj: obj.__dict__,
        sort_keys=True, ensure_ascii=False,
        indent=4)

# 删除上传视频接口
@app.route('/easy/delete_video', methods=['POST'])
@require_auth
def delete_video():
    try:
        # 解析请求数据
        request_data = json.loads(request.data)
        
        # 检查必要参数
        if 'filename' not in request_data or not request_data['filename']:
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件名参数缺失', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        filename = request_data['filename']
        file_path = os.path.join('/code/data/mp4', filename)  # 修改为/code/data/mp4
        
        # 检查文件是否存在
        if not os.path.exists(file_path):
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件不存在', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 获取文件信息用于返回
        file_size = os.path.getsize(file_path)
        
        # 格式化文件大小
        size_str = f"{file_size} 字节"
        if file_size > 1024 * 1024 * 1024:
            size_str = f"{file_size / (1024 * 1024 * 1024):.2f} GB"
        elif file_size > 1024 * 1024:
            size_str = f"{file_size / (1024 * 1024):.2f} MB"
        elif file_size > 1024:
            size_str = f"{file_size / 1024:.2f} KB"
        
        # 删除文件
        os.remove(file_path)
        
        return json.dumps(
            EasyResponse(ResponseCode.success.value[0], True, '视频删除成功', {
                'filename': filename,
                'size_deleted': size_str,
                'delete_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
            }),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    
    except Exception as e:
        logger.error(f"删除视频文件时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)

# 删除上传音频接口
@app.route('/easy/delete_audio', methods=['POST'])
@require_auth
def delete_audio():
    try:
        # 解析请求数据
        request_data = json.loads(request.data)
        
        # 检查必要参数
        if 'filename' not in request_data or not request_data['filename']:
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件名参数缺失', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        filename = request_data['filename']
        file_path = os.path.join('/code', 'data', 'voice', 'data', filename)
        
        # 检查文件是否存在
        if not os.path.exists(file_path):
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件不存在', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 获取文件信息用于返回
        file_size = os.path.getsize(file_path)
        
        # 格式化文件大小
        size_str = f"{file_size} 字节"
        if file_size > 1024 * 1024 * 1024:
            size_str = f"{file_size / (1024 * 1024 * 1024):.2f} GB"
        elif file_size > 1024 * 1024:
            size_str = f"{file_size / (1024 * 1024):.2f} MB"
        elif file_size > 1024:
            size_str = f"{file_size / 1024:.2f} KB"
        
        # 删除文件
        os.remove(file_path)
        
        return json.dumps(
            EasyResponse(ResponseCode.success.value[0], True, '音频删除成功', {
                'filename': filename,
                'size_deleted': size_str,
                'delete_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
            }),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    
    except Exception as e:
        logger.error(f"删除音频文件时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)

# GPU显存释放接口
@app.route('/easy/clear_gpu_memory', methods=['POST'])
@require_auth
def clear_gpu_memory():
    try:
        # 强制进行Python的垃圾回收
        gc.collect()
        
        # 尝试使用torch清理GPU缓存（如果可用）
        try:
            import torch
            if torch.cuda.is_available():
                torch.cuda.empty_cache()
                initial_memory = torch.cuda.memory_allocated()
                initial_memory_mb = initial_memory / (1024 * 1024)
                
                # 释放所有未使用的缓存内存
                torch.cuda.empty_cache()
                
                # 再次检查内存使用情况
                final_memory = torch.cuda.memory_allocated()
                final_memory_mb = final_memory / (1024 * 1024)
                
                # 计算释放的内存
                freed_memory = initial_memory - final_memory
                freed_memory_mb = freed_memory / (1024 * 1024)
                
                return json.dumps(
                    EasyResponse(ResponseCode.success.value[0], True, 'GPU显存释放成功', {
                        'initial_memory': f"{initial_memory_mb:.2f} MB",
                        'final_memory': f"{final_memory_mb:.2f} MB",
                        'freed_memory': f"{freed_memory_mb:.2f} MB",
                        'gpu_info': str(torch.cuda.get_device_properties(0)),
                        'timestamp': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
                    }),
                    default=lambda obj: obj.__dict__,
                    sort_keys=True, ensure_ascii=False,
                    indent=4)
            else:
                return json.dumps(
                    EasyResponse(ResponseCode.success.value[0], True, 'GPU不可用', {
                        'message': '没有检测到CUDA设备',
                        'timestamp': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
                    }),
                    default=lambda obj: obj.__dict__,
                    sort_keys=True, ensure_ascii=False,
                    indent=4)
                
        except ImportError:
            # 如果torch不可用，返回相应信息
            return json.dumps(
                EasyResponse(ResponseCode.success.value[0], True, '已执行内存回收', {
                    'message': 'Python垃圾收集已完成，但PyTorch不可用，无法清理GPU缓存',
                    'timestamp': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
                }),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
    
    except Exception as e:
        logger.error(f"释放GPU显存时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)

# 列出结果目录文件接口
@app.route('/easy/list_result', methods=['GET'])
@require_auth
def list_result():
    try:
        # 获取请求参数
        keyword = request.args.get('keyword', '')
        page = int(request.args.get('page', 1))
        page_size = int(request.args.get('pageSize', 10))
        
        # 确保页码和每页数量为正数
        if page < 1:
            page = 1
        if page_size < 1:
            page_size = 10
        
        # 获取结果文件目录
        result_dir = os.path.join('/code', 'data', 'result')
        
        # 确保目录存在
        if not os.path.exists(result_dir):
            return json.dumps({
                'code': ResponseCode.success.value[0],
                'message': '成功',
                'data': {
                    'rows': [],
                    'page': page,
                    'pageSize': page_size,
                    'totalPages': 0
                }
            }, ensure_ascii=False)
        
        # 获取所有结果文件
        all_files = []
        for file_name in os.listdir(result_dir):
            file_path = os.path.join(result_dir, file_name)
            if os.path.isfile(file_path):
                # 获取文件信息
                file_stats = os.stat(file_path)
                
                # 格式化文件大小
                file_size = file_stats.st_size
                size_str = f"{file_size} 字节"
                if file_size > 1024 * 1024 * 1024:
                    size_str = f"{file_size / (1024 * 1024 * 1024):.2f} GB"
                elif file_size > 1024 * 1024:
                    size_str = f"{file_size / (1024 * 1024):.2f} MB"
                elif file_size > 1024:
                    size_str = f"{file_size / 1024:.2f} KB"
                
                file_info = {
                    'fileName': file_name,
                    'filePath': f'/code/data/result/{file_name}',
                    'fileSize': file_size,
                    'fileSizeStr': size_str,
                    'createTime': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(file_stats.st_ctime))
                }
                # 根据关键词过滤
                if keyword.lower() in file_name.lower():
                    all_files.append(file_info)
        
        # 计算总页数
        total_files = len(all_files)
        total_pages = (total_files + page_size - 1) // page_size
        
        # 对文件按创建时间排序（最新的在前）
        all_files.sort(key=lambda x: x['createTime'], reverse=True)
        
        # 分页
        start_idx = (page - 1) * page_size
        end_idx = min(start_idx + page_size, total_files)
        page_files = all_files[start_idx:end_idx]
        
        # 返回结果
        return json.dumps({
            'code': ResponseCode.success.value[0],
            'message': '成功',
            'data': {
                'rows': page_files,
                'page': page,
                'pageSize': page_size,
                'totalPages': total_pages,
                'total': total_files
            }
        }, ensure_ascii=False)
    
    except Exception as e:
        # 处理异常
        logger.error(f"列出结果文件时出错: {str(e)}")
        traceback.print_exc()
        return json.dumps({
            'code': ResponseCode.system_error.value[0],
            'message': ResponseCode.system_error.value[1],
            'data': {
                'rows': [],
                'page': 1,
                'pageSize': 10,
                'totalPages': 0
            }
        }, ensure_ascii=False)

# 下载结果文件接口
@app.route('/easy/download_result/<filename>', methods=['GET'])

def download_result(filename):
    try:
        # 构建文件路径
        file_path = os.path.join('/code', 'data', 'result', filename)
        
        # 检查文件是否存在
        if not os.path.exists(file_path):
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件不存在', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 使用Flask的send_file函数提供文件下载
        from flask import send_file
        return send_file(file_path, 
                         mimetype='application/octet-stream',
                         as_attachment=True,
                         download_name=filename)
    
    except Exception as e:
        logger.error(f"下载结果文件时出错: {str(e)}")
        traceback.print_exc()
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)

# 删除结果文件接口
@app.route('/easy/delete_result', methods=['POST'])
@require_auth
def delete_result():
    try:
        # 解析请求数据
        request_data = json.loads(request.data)
        
        # 检查必要参数
        if 'filename' not in request_data or not request_data['filename']:
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件名参数缺失', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        filename = request_data['filename']
        file_path = os.path.join('/code', 'data', 'result', filename)
        
        # 检查文件是否存在
        if not os.path.exists(file_path):
            return json.dumps(
                EasyResponse(ResponseCode.error1.value[0], False, '文件不存在', {}),
                default=lambda obj: obj.__dict__,
                sort_keys=True, ensure_ascii=False,
                indent=4)
        
        # 获取文件信息用于返回
        file_size = os.path.getsize(file_path)
        
        # 格式化文件大小
        size_str = f"{file_size} 字节"
        if file_size > 1024 * 1024 * 1024:
            size_str = f"{file_size / (1024 * 1024 * 1024):.2f} GB"
        elif file_size > 1024 * 1024:
            size_str = f"{file_size / (1024 * 1024):.2f} MB"
        elif file_size > 1024:
            size_str = f"{file_size / 1024:.2f} KB"
        
        # 删除文件
        os.remove(file_path)
        
        return json.dumps(
            EasyResponse(ResponseCode.success.value[0], True, '结果文件删除成功', {
                'filename': filename,
                'size_deleted': size_str,
                'delete_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
            }),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)
    
    except Exception as e:
        logger.error(f"删除结果文件时发生错误: {str(e)}")
        logger.error(traceback.format_exc())
        return json.dumps(
            EasyResponse(ResponseCode.system_error.value[0], False, f'系统异常: {str(e)}', {}),
            default=lambda obj: obj.__dict__,
            sort_keys=True, ensure_ascii=False,
            indent=4)

# 主程序入口
if __name__ == '__main__':
    a()  # 调用初始化函数
    TransDhTask.instance()  # 初始化任务实例
    time.sleep(15)  # 等待系统初始化
    logger.info("******************* TransDhServer服务启动 *******************")
    logger.info(f"认证Token已加载，默认值：{AUTH_TOKEN if AUTH_TOKEN == 'heygam778899' else '自定义值'}")
    # 创建必要的目录
    if not os.path.exists(GlobalConfig.instance().temp_dir):
        logger.info("创建临时目录")
        os.makedirs(GlobalConfig.instance().temp_dir)
    if not os.path.exists(GlobalConfig.instance().result_dir):
        logger.info("创建结果目录")
        os.makedirs(GlobalConfig.instance().result_dir)

    # 启动Flask服务
    app.run(
        host=str(GlobalConfig.instance().server_ip),
        port=int(GlobalConfig.instance().server_port),
        debug=False,
        threaded=False)

# 扩展TransDhTask类，添加webhook_urls字典
class TransDhTask:
    _instance = None
    
    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance
    
    def __init__(self):
        if TransDhTask._instance is not None:
            raise Exception("This class is a singleton!")
        else:
            TransDhTask._instance = self
            self.run_flag = False
            self.run_lock = threading.Lock()
            self.task_dic = {}
            self.webhook_urls_dic = {}  # 添加webhook_urls字典
